using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Xml;
using System.Xml.Schema;
using System.Xml.Serialization;
using Microsoft.Practices.ObjectBuilder;


namespace ObjectContainer.Configuration
{

    public partial class BuilderXmlConfig : IBuilderConfigurator<BuilderStage>
    {
        private IBuilder<BuilderStage> _builder;
        private BuilderXmlConfigElement _config;

        public BuilderXmlConfig(string xml)
        {
            _config = ParseXmlConfiguration(xml);
        }

        public void ApplyConfiguration(IBuilder<BuilderStage> builder)
        {
            _builder = builder;

            if (_config.Mappings != null)
                foreach (MappingElement mapping in _config.Mappings)
                    ProcessMapping(mapping);

            if (_config.BuildRules != null)
            {
                foreach (BuildRuleElement buildRule in _config.BuildRules)
                {
                    Type buildType = Type.GetType(buildRule.Type);
                    Debug.Assert(buildType != null);

                    ProcessConstructor(buildRule, buildType);
                    ProcessProperties(buildRule, buildType);
                    ProcessMethods(buildRule, buildType);
                    ProcessSingleton(buildRule, buildType);
                }
            }
        }

        private void ProcessMapping(MappingElement mapping)
        {
            Type fromType = Type.GetType(mapping.FromType);
            Type toType = Type.GetType(mapping.ToType);

            Debug.Assert(fromType != null);
            Debug.Assert(toType != null);

            _builder.Policies.Set<ITypeMappingPolicy>(new TypeMappingPolicy(toType, null), fromType, null);
        }

        private void ProcessMethods(BuildRuleElement buildRule, Type buildType)
        {
            if (buildRule.Method == null)
                return;

            MethodPolicy policy = GetMethodPolicy(buildType, null);

            foreach (MethodElement method in buildRule.Method)
            {
                List<IParameter> parameters = new List<IParameter>();

                foreach (object param in method.Items)
                    parameters.Add(GetParameterFromConfigParam(param));

                policy.Methods.Add(method.Name, new MethodCallInfo(method.Name, parameters));
            }

        }

        private MethodPolicy GetMethodPolicy(Type typeToBuild, string idToBuild)
        {
            MethodPolicy policy = _builder.Policies.Get<IMethodPolicy>(typeToBuild, idToBuild) as MethodPolicy;

            if (policy == null)
            {
                policy = new MethodPolicy();
                _builder.Policies.Set<IMethodPolicy>(policy, typeToBuild, idToBuild);
            }

            return policy;
        }

        private void ProcessProperties(BuildRuleElement buildRule, Type buildType)
        {
            if (buildRule.Property == null)
                return;

            PropertySetterPolicy policy = new PropertySetterPolicy();

            foreach (PropertyElement prop in buildRule.Property)
                policy.Properties.Add(prop.Name, new PropertySetterInfo(prop.Name, GetParameterFromConfigParam(prop.Item)));

            _builder.Policies.Set<IPropertySetterPolicy>(policy, buildType, null);
        }

        private void ProcessSingleton(BuildRuleElement buildRule, Type buildType)
        {
            if (buildRule.Mode == ModeElement.Singleton)
                _builder.Policies.Set<ISingletonPolicy>(new SingletonPolicy(true), buildType, null);
        }

        private void ProcessConstructor(BuildRuleElement buildRule, Type buildType)
        {
            if (buildRule.Constructor == null)
                return;

            ConstructorPolicy policy = new ConstructorPolicy();

            foreach (object param in buildRule.Constructor.Items)
                policy.AddParameter(GetParameterFromConfigParam(param));

            _builder.Policies.Set<ICreationPolicy>(policy, buildType, null);
        }

        private static IParameter GetParameterFromConfigParam(object param)
        {
            if (param is ValueElement)
                return ValueElementToParameter(param as ValueElement);
            else if (param is RefElement)
                return RefElementToParameter(param as RefElement);

            Debug.Assert(false);
            return null;
        }

        private static IParameter RefElementToParameter(RefElement refParam)
        {
            Type paramType = Type.GetType(refParam.Type);
            Debug.Assert(paramType != null);
            return new CreationParameter(paramType, null);
        }

        private static IParameter ValueElementToParameter(ValueElement valueParam)
        {
            Type paramType = Type.GetType(valueParam.Type);
            Debug.Assert(paramType != null);
            return new ValueParameter(paramType, Convert.ChangeType(valueParam.Value, paramType));
        }

        private static BuilderXmlConfigElement ParseXmlConfiguration(string config)
        {
            XmlSerializer ser = new XmlSerializer(typeof(BuilderXmlConfigElement));
            StringReader stringReader = new StringReader(config);
            XmlSchema schema =
                XmlSchema.Read(
                    Assembly.GetExecutingAssembly().GetManifestResourceStream(
                        "ObjectContainer.Configuration.BuilderXmlConfigElement.xsd"), null);
            XmlReaderSettings settings = new XmlReaderSettings();
            settings.ValidationType = ValidationType.Schema;
            settings.Schemas.Add(schema);
            XmlReader reader = XmlReader.Create(stringReader, settings);
            BuilderXmlConfigElement configData = (BuilderXmlConfigElement)ser.Deserialize(reader);
            return configData;
        }
    }
}
